#include "stdafx.h"
#include "World.h"

#include "Components\TileComponent.h"
#include "Components\GaulemRenderableComponent.h"
#include "BoundingBoxComponent.h"

using namespace ZeEngine;
using namespace std;

World::World(GameEngine& gameEngine) : m_entityManager(gameEngine.GetEntityManager())
{
}

World::~World()
{
}

bool World::Init(GameEngine& gameEngine)
{
    ResourceManager& resourceManager = gameEngine.GetResourceManager();

    GetAllTextures(resourceManager);

    CreateWorld();

    return true;
}

void World::GetAllTextures(ResourceManager& resourceManager)
{
    for (int i = 0; i < eRoomTypeLast; ++i)
    {
        m_textures[i] = GetTextureFromRoom(static_cast<World::RoomType>(i), resourceManager);
    }
}

void World::CreateWorld()
{
    //Create a world of just dirt for now
    for (int i = 0; i < cWorldWidth; ++i)
    {
        for (int j = 0; j < cWorldHeight; ++j)
        {
            if (j <=2)
            {
                AddRoom(eGrass, Vector2D<u32>(i * TileComponent::cTileWidth, j * TileComponent::cTileHeight));
            }
            else if (j == 4 && i <= 10)
            {
                AddRoom(e128, Vector2D<u32>(i * TileComponent::cTileWidth, j * TileComponent::cTileHeight));
            }
            else
            {
                AddRoom(eDirt, Vector2D<u32>(i * TileComponent::cTileWidth, j * TileComponent::cTileHeight));
            }
        }
    }
}

bool World::AddRoom(RoomType type, const Vector2D<u32>& position)
{
    return AddRoom(type, position.GetX(), position.GetY());
}

bool World::AddRoom(RoomType type, u32 xPos, u32 yPos)
{
    bool fSuccess = false;

    SDLTexture* pTexture = m_textures[type];
    stringstream ss;
    Entity* pEntity = NULL;
    const u32 xTile = xPos / TileComponent::cTileWidth;
    const u32 yTile = yPos / TileComponent::cTileHeight;

    ss << "room_" << pTexture->GetName() << " " << xTile << "," << yTile;

    vector<Entity*> collidingRooms;

    if (pTexture)
    {
        Vector2D<f32> dimension = pTexture->GetDimension();
        Vector2D<u32> position = Vector2D<u32>(xTile * TileComponent::cTileWidth, yTile * TileComponent::cTileHeight);

        //Temporarily remove rooms 
        if (m_entityManager.GetEntityDataStore("bottom").HasIntersectingEntities(position, dimension))
        {
            int xTilesToRemove = (dimension.GetX() / TileComponent::cTileWidth) - 1;
            int yTilesToRemove = (dimension.GetY() / TileComponent::cTileHeight) - 1;

            for (int i = 0; i <= xTilesToRemove; ++i)
            {
                for (int j = 0; j <= yTilesToRemove; ++j)
                {
                    RemoveRoom(xPos + (i * TileComponent::cTileWidth), yPos + (j * TileComponent::cTileHeight));
                }
            }
        }

        if (!m_entityManager.GetEntityDataStore("bottom").HasIntersectingEntities(position, dimension))
        {
            pEntity = m_entityManager.CreateAndAddEntity(ss.str(), "bottom");
            if (pEntity)
            {
                pEntity->AddComponent(make_unique<TransformComponent>(Vector2D<u32>(xTile * TileComponent::cTileWidth, yTile * TileComponent::cTileHeight)));
                pEntity->AddComponent(make_unique<GaulemRenderableComponent>(false));
                pEntity->AddComponent(make_unique<SpriteComponent>(pTexture));
                pEntity->AddComponent(make_unique<BoundingBoxComponent>(dimension));
                pEntity->AddComponent(make_unique<TileComponent>(Vector2D<u32>(xTile, yTile), dimension));

                fSuccess = true;
            }
            else
            {
                LOG_ERROR("Error creating tile entity");
            }
        }
        else
        {
            LOG_WARNING("Intersecting rooms");
        }
    }
    
    if (fSuccess)
    {
        SetRoom(xTile, yTile, pEntity, false);
    }
    else
    {
        LOG_ERROR("Error adding room");
    }

    return fSuccess;
}

void World::SetRoom(u32 xTile, u32 yTile, Entity* pEntity, bool fClear)
{
    //Update the world
    u32 x = xTile;
    u32 y = yTile;

    auto pDimension = pEntity->GetComponent<BoundingBoxComponent>();

    if (pDimension)
    {
        u32 xSpan = static_cast<u32>(pDimension->GetDimension().GetX()) / TileComponent::cTileWidth;
        u32 ySpan = static_cast<u32>(pDimension->GetDimension().GetY()) / TileComponent::cTileHeight;

        //Update all associated tiles for this room
        for (u32 i = 0; i < xSpan; ++i)
        {
            y = yTile;
            for (u32 j = 0; j < ySpan; ++j)
            {
                m_world[x][y] = fClear ? NULL : pEntity;
                y++;
            }
            x++;
        }
    }
    else
    {
        LOG_ERROR("Error getting dimension entity");
    }

    if (fClear)
    {
        pEntity->SetToBeRemoved();
    }
}

void World::RemoveRoom(const ZeEngine::Vector2D<u32>& position)
{
    RemoveRoom(position.GetX(), position.GetY());
}

void World::RemoveRoom(u32 xPos, u32 yPos)
{
    const u32 xTile = xPos / TileComponent::cTileWidth;
    const u32 yTile = yPos / TileComponent::cTileHeight;
    auto pEntity = m_world[xTile][yTile];
    
    if (pEntity)
    {
        auto component = pEntity->GetComponent<TileComponent>();
        SetRoom(component->GetWorldIndex().GetX(), component->GetWorldIndex().GetY(), pEntity, true);
    }
}

void World::RemoveRoom(const Entity& room)
{
    auto position = room.GetComponent<TransformComponent>();

    if (position)
    {
        RemoveRoom(static_cast<u32>(position->GetPosition().GetX()), static_cast<u32>(position->GetPosition().GetY()));
    }
    else
    {
        LOG_ERROR("Error getting position for room " + room.GetName());
    }
}

Entity* World::GetRoom(const Vector2D<u32>& position)
{
    return GetRoom(position.GetX(), position.GetY());
}

Entity* World::GetRoom(u32 xPos, u32 yPos)
{    
    const u32 xTile = xPos / TileComponent::cTileWidth;
    const u32 yTile = yPos / TileComponent::cTileHeight;
    Entity* pRoom = NULL;

    if (xTile >= 0 && yTile >= 0 && xTile < cWorldWidth && yTile < cWorldHeight)
    {
        pRoom = m_world[xTile][yTile];
    }
    else
    {
        LOG_WARNING("Invalid position");
    }

    return pRoom;
}
void World::GetPath(const Entity* pStartRoom, const Entity* pEndroom, queue<Entity*>& path)
{
    auto startPosition = pStartRoom->GetComponent<TransformComponent>()->GetPosition();
    auto endPosition = pEndroom->GetComponent<TransformComponent>()->GetPosition();

    return GetPath(startPosition, endPosition, path);
}

void World::GetPath(const Vector2D<s32>& startPosition, const Vector2D<s32>& endPosition, queue<Entity*>& path)
{
    Entity* pStartRoom = GetRoom(startPosition);
    Entity* pEndRoom = GetRoom(endPosition);
    Entity* pNextRoom = pStartRoom;
    NextRoom direction = eNone;

    //Ez for now just left or right
    if (endPosition.GetX() > startPosition.GetX())
    {
        direction = eRight;
    }
    else if (endPosition.GetX() < startPosition.GetX())
    {
        direction = eLeft;
    }
    else
    {
        LOG_ERROR("Cannot find path");
    }

    if (direction != eNone)
    {
        bool fDone = false;
        path.push(pStartRoom);
        while (pNextRoom && pNextRoom != pEndRoom && !fDone)
        {
            pNextRoom = GetNextRoom(pNextRoom, direction);

            if (pNextRoom)
            {
                path.push(pNextRoom);
            }
        } 
    }
}

Entity* World::GetNextRoom(Entity* pRoom, NextRoom nextRoom)
{
    Entity* pNewRoom = NULL;
    auto pComponent = pRoom->GetComponent<TileComponent>();
    Vector2D<u32> index = pComponent->GetWorldIndex();
    Vector2D<s32> increment = pComponent->GetWorldSpan();

    bool fFound = false;

    if (nextRoom == eUp)
    {
        increment *= Vector2D<s32>(0, -1);
    }
    else if (nextRoom == eDown)
    {
        increment *= Vector2D<s32>(0, 1);
    }
    else if (nextRoom == eRight)
    {
        increment *= Vector2D<s32>(1, 0);
    }
    else if (nextRoom == eLeft)
    {
        increment *= Vector2D<s32>(-1, 0);
    }

    Vector2D<s32> newIndex = index + increment;

    if (newIndex.GetX() < cWorldWidth && newIndex.GetX() >= 0 && 
        newIndex.GetY() < cWorldHeight && newIndex.GetY() >= 0)
    {
        pNewRoom = m_world[newIndex.GetX()][newIndex.GetY()];

        if (pNewRoom->GetID() != pRoom->GetID())
        {
            fFound = true;
        }
        newIndex += increment;
    } 

    return pNewRoom;
}

SDLTexture* World::GetTextureFromRoom(RoomType type, ResourceManager& resourceManager)
{
    SDLTexture* pTexture = NULL;
    switch (type)
    {
    case eSleepingQuarter:
        pTexture = resourceManager.GetResource<SDLTexture>("128", "temp_tiles");
        break;
    case eKitchen:
        pTexture = resourceManager.GetResource<SDLTexture>("128", "temp_tiles");
        break;
    case eLaboratory:
        pTexture = resourceManager.GetResource<SDLTexture>("128", "temp_tiles");
        break;
    case eDirt:
        pTexture = resourceManager.GetResource<SDLTexture>("dirt", "temp_tiles");
        break;
    //temp rooms
    case e64:
        pTexture = resourceManager.GetResource<SDLTexture>("dirt", "temp_tiles");
        break; 
    case e128:
        pTexture = resourceManager.GetResource<SDLTexture>("128", "temp_tiles");
        break;
    case eGrass:
        pTexture = resourceManager.GetResource<SDLTexture>("grass", "temp_tiles");
        break;
    default:
        LOG_WARNING("Unknown Room Type");
    }
    return pTexture;
}